package ph.com.gs3.formalistics.services.synchronizers;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;

import ph.com.gs3.formalistics.global.constants.LoggingType;
import ph.com.gs3.formalistics.global.utilities.logging.FLLogger;
import ph.com.gs3.formalistics.model.LocalDataAccessObject.InsertFailedException;
import ph.com.gs3.formalistics.model.RemoteDataAccessObject.RDAOCommunicationException;
import ph.com.gs3.formalistics.model.facades.FormsDataWriter;
import ph.com.gs3.formalistics.model.ldao.FormsLDAO;
import ph.com.gs3.formalistics.model.ldao.UsersLDAO;
import ph.com.gs3.formalistics.model.rdao.FormsRDAO;
import ph.com.gs3.formalistics.model.vo.application.APIResponse;
import ph.com.gs3.formalistics.model.vo.application.APIResponse.APIInvalidResponseException;
import ph.com.gs3.formalistics.model.vo.application.APIResponse.APIServerErrorException;
import ph.com.gs3.formalistics.model.vo.business.User;
import ph.com.gs3.formalistics.model.vo.business.form.Form;
import ph.com.gs3.formalistics.model.vo.business.form.FormFieldData.InvalidFormFieldException;
import ph.com.gs3.formalistics.model.vo.parsers.json.FormJSONParser;
import ph.com.gs3.formalistics.services.synchronizers.exceptions.SynchronizationFailedException;
import android.content.Context;

/**
 * Facade class for functions used to synchronize forms from formalistics web to local
 * database.
 * 
 * @author Ervinne Sodusta
 * 
 */
public class FormsSynchronizer extends AbstractSynchronizer<List<Form>> {

	public static final String TAG = FormsSynchronizer.class.getSimpleName();
	public static LoggingType LOGGING_TYPE;

	protected FormsDataWriter formsDataWriter;
	protected UsersLDAO usersLDAO;
	protected FormsLDAO formsLDAO;
	protected FormsRDAO formsRDAO;

	protected User user;

	public FormsSynchronizer(Context context, User user) {
		super(TAG, LOGGING_TYPE == null ? LoggingType.DISABLED : LOGGING_TYPE);
		this.user = user;

		if (user.getCompany() == null) {
			throw new RuntimeException("User company must not be null");
		}

		formsDataWriter = new FormsDataWriter(context);

		usersLDAO = new UsersLDAO(context);
		formsLDAO = new FormsLDAO(context);
		formsRDAO = new FormsRDAO(user.getCompany().getServer());

	}

	/**
	 * Downloads all form updates from the server starting from the last update date and
	 * saves the updates to the database.
	 * 
	 * @return The list of forms updated after calling this method
	 * 
	 * @throws SynchronizationFailedException
	 */
	public List<Form> synchronize() throws SynchronizationFailedException {

		if (user == null) {
			// Unrecoverable error
			throw new RuntimeException("Cannot run synchronizer without a user");
		}

		try {
			APIResponse updatedFormsAPIResponse = getUpdatedForms();
			List<Form> updatedForms = saveFormsFromAPIResponse(updatedFormsAPIResponse);

			String lastUpdateDate = updatedFormsAPIResponse.getServerDate();
			updateUserFormsLastUpdate(lastUpdateDate);

			return updatedForms;
		} catch (APIInvalidResponseException | APIServerErrorException | RDAOCommunicationException e) {
			throw new SynchronizationFailedException(e);
		}

	}

	/**
	 * Gets the new and newly updated forms from the Formalistics' server.
	 * 
	 * @return The response (APIResponse object) of the server from getting the updated
	 *         forms.
	 * @throws APIInvalidResponseException
	 * @throws APIServerErrorException
	 * @throws RDAOCommunicationException
	 */
	protected APIResponse getUpdatedForms() throws APIInvalidResponseException,
	        APIServerErrorException, RDAOCommunicationException {

		APIResponse downloadResponse;
		String lastFormsUpdate = null;

		int companyId = user.getCompany().getId();

		formsLDAO.open();

		// Download the new forms. If there are existing forms in the database, get only
		// the new and newly updated forms.
		if (formsLDAO.getFormsCount(companyId) > 0) {
			lastFormsUpdate = user.getFormsLastUpdateDate();
		}

		formsLDAO.close();

		// Throws APIInvalidResponseException, CommunicatorException
		downloadResponse = formsRDAO.getForms(lastFormsUpdate);

		if (downloadResponse.isOperationSuccessful()) {
			return downloadResponse;
		} else {
			throw new APIServerErrorException(downloadResponse.getErrorMessage());
		}

	}

	/**
	 * Parses the API's response (APIResponse object) to a list of forms and saves it to
	 * the database.
	 * 
	 * @param APIResponseForms
	 *            The API's response from getting the updated documents.
	 * @return A list of Form objects saved.
	 * @throws APIInvalidResponseException
	 */
	protected List<Form> saveFormsFromAPIResponse(APIResponse APIResponseForms)
	        throws APIInvalidResponseException {

		List<Form> forms = new ArrayList<>();

		String resultsRaw = APIResponseForms.getResults();

		JSONArray results;
		try {
			results = new JSONArray(resultsRaw);
		} catch (JSONException e) {
			throw new APIInvalidResponseException(
			        "Unable to read the JSON object (JSON Array) containing the list of forms, it may be corrupted.",
			        e);
		}

		for (int i = 0; i < results.length(); i++) {

			formsLDAO.open();

			try {
				Form form = FormJSONParser.createFromJSON(results.getJSONObject(i));
				form.setCompany(user.getCompany());

				log("Now saving form: " + form.getName());
				forms.add(formsDataWriter.saveForm(form));
			} catch (JSONException | InvalidFormFieldException e) {
				FLLogger.e(TAG, e.getMessage());
				throw new APIInvalidResponseException(
				        "Unable to read the JSON object containing the 1 form, it may be corrupted.",
				        e);
			} finally {
				formsLDAO.close();
			}

		}

		return forms;

	}

	/**
	 * Updates the form's last updated date of the user. If the update failed, this method
	 * will log a warning about it.
	 * 
	 * @param updateDate
	 *            The new date when the forms are updated. This must be based on the
	 *            server date and time.
	 */
	protected void updateUserFormsLastUpdate(String updateDate) {

		usersLDAO.open();
		try {
			usersLDAO.setFormsLastUpdate(user.getId(), updateDate);
		} catch (InsertFailedException e) {
			// Code level exception
			FLLogger.w(TAG, "Saving of a last forms update failed. " + e.getMessage());
		} finally {
			usersLDAO.close();
		}

	}

}
